﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

namespace Macds.Core.XML
{
    public class XMLSchemaClassBuilderManager
    {
        private List<ClassSchema> classes;
        private List<PropertySchema> properties;
        private ITextBuilder builder;
        private string xmlPath;
        private XElement xmlData;
        private int counter;

        public XMLSchemaClassBuilderManager(string xmlInfoStr)
        {
            classes = new List<ClassSchema>();
            properties = new List<PropertySchema>();
            builder = new DefaultClassBuilder();
            xmlPath = xmlInfoStr;
            RootClassType = "";
            counter = 0;
        }

        public XMLSchemaClassBuilderManager(ITextBuilder customBuilder, string xmlInfoStr)
        {
            classes = new List<ClassSchema>();
            properties = new List<PropertySchema>();
            builder = customBuilder;
            xmlPath = xmlInfoStr;
            RootClassType = "";
            counter = 0;
        }

        #region Public Properties

        public string RootClassType { get; set; }
        public bool IsXMLString { get; set; }

        #endregion

        #region Public methods

        public string GetClassesAtOnce()
        {
            var data = GetClasses();

            var sb = new StringBuilder();
            foreach (var kv in data.Values)
            {
                sb.Append(kv + "\r\n\r\n");
            }

            return sb.ToString();
        }

        public Dictionary<string, string> GetClasses()
        {
            GetSchemaInformation();
            var list = new Dictionary<string, string>();

            foreach (var schema in classes)
            {
                list.Add(schema.Name, BuildClassText(schema));
            }

            return list;
        }

        public string BuildClassText(ClassSchema classSchema)
        {
            return builder.BuildClassText(classSchema);
        }

        public ClassSchema GetSchemaInformation()
        {
            if (IsXMLString)
            {
                xmlData = XElement.Parse(xmlPath);
            }
            else
            {
                xmlData = XElement.Load(xmlPath);
            }

            var root = RemoveAllNamespaces(xmlData);
            RootClassType = root.Name.LocalName;
            var classSchema = GetClassSchema(root);

            return classSchema;
        }

        public string RemoveAllNamespaces()
        {
            XElement xmlDocumentWithoutNs = RemoveAllNamespaces(xmlData);

            return xmlDocumentWithoutNs.ToString();
        }

        public XElement RemoveAllNamespaces(XElement xmlDocument)
        {
            XElement element = new XElement(xmlDocument.Name.LocalName);

            if (!xmlDocument.HasElements)
            {
                element.Value = xmlDocument.Value;

                foreach (XAttribute attribute in xmlDocument.Attributes())
                {
                    if (attribute.Name.LocalName.IndexOf(":") == -1)
                    {
                        element.Add(attribute);
                    }
                }

                return element;
            }
            else
            {
                element = new XElement(xmlDocument.Name.LocalName, xmlDocument.Elements().Select(el => RemoveAllNamespaces(el)));
                foreach (XAttribute attribute in xmlDocument.Attributes())
                {
                    if (attribute.Name.LocalName.IndexOf(":") == -1)
                    {
                        element.Add(attribute);
                    }
                }
            }

            return element;
        }

        public ClassSchema GetClassSchema(XElement root)
        {
            if (!String.IsNullOrEmpty(root.Name.LocalName))
            {
                #region Create Schema (xsd)
                
                var reader = root.CreateReader();
                var schemaSet = new XmlSchemaInference().InferSchema(reader);

                var documents = new List<XDocument>();
                foreach (XmlSchema schema in schemaSet.Schemas())
                {
                    var doc = new XDocument();
                    using (XmlWriter xw = doc.CreateWriter())
                    {
                        schema.Write(xw);
                    }

                    documents.Add(doc);
                }

                var xmlStr = documents.Count > 0 ? documents[0].ToString() : "";

                #endregion

                if (!String.IsNullOrEmpty(xmlStr))
                {
                    var xsdRoot = XElement.Parse(xmlStr);
                    var initial = RemoveAllNamespaces(xsdRoot.Elements().First());

                    var main = GetChildClassSchema(initial, true);

                    return main;
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        public ClassSchema GetChildClassSchema(XElement initial, bool isRoot = false)
        {
            var main = new ClassSchema();
            main.Name = isRoot ? initial.Attribute("name").Value : initial.Attribute("name").Value + "Class";

            if (classes.FirstOrDefault(c => c.Name.Trim().ToLower() == main.Name.Trim().ToLower()) == null)
            {
                classes.Add(main);
            }

            var complexType = initial.Element("complexType");
            var sequence = complexType.Element("sequence");

            if (sequence == null)
            {
                // Get attributes without elements

                sequence = complexType.Element("simpleContent");
                if (sequence == null)
                {
                    // Get attributes of the element without value
                    foreach (var attribute in complexType.Elements())
                    {
                        if (attribute.Name.LocalName == "attribute")
                        {
                            main.Properties.Add(new PropertySchema()
                            {
                                Type = PropertyType.String,
                                XmlType = PropertyXmlType.Attribute,
                                Name = attribute.Attribute("name").Value
                            });
                        }
                    }
                }
                else
                {
                    // Get attributes of the elements with values
                    main.Properties.Add(new PropertySchema()
                    {
                        Type = PropertyType.String,
                        Name = "Value"
                    });

                    var extension = sequence.Element("extension");
                    foreach (var attribute in extension.Elements())
                    {
                        if (attribute.Name.LocalName == "attribute")
                        {
                            main.Properties.Add(new PropertySchema()
                            {
                                Type = PropertyType.String,
                                XmlType = PropertyXmlType.Attribute,
                                Name = attribute.Attribute("name").Value
                            });
                        }
                    }
                }
            }
            else
            {
                // Get attributes
                var checkAttributes = complexType.Descendants("attribute").Any();

                if (checkAttributes)
                {
                    foreach (var attribute in complexType.Elements())
                    {
                        if (attribute.Name.LocalName == "attribute")
                        {
                            main.Properties.Add(new PropertySchema()
                            {
                                Type = PropertyType.String,
                                XmlType = PropertyXmlType.Attribute,
                                Name = attribute.Attribute("name").Value
                            });
                        }
                    }
                }

                // Get elements single and custom
                foreach (XElement element in sequence.Elements())
                {
                    if (element.Elements().Count() == 0)
                    {
                        if (element.Attribute("maxOccurs") == null)
                        {
                            // Get list of string
                            main.Properties.Add(new PropertySchema()
                            {
                                Type = PropertyType.String,
                                Name = element.Attribute("name").Value
                            });
                        }
                        else
                        {
                            main.Properties.Add(new PropertySchema()
                            {
                                Type = PropertyType.List,
                                XmlType = PropertyXmlType.Element,
                                Name = element.Attribute("name").Value
                            });
                        }
                    }
                    else
                    {
                        if (element.Attribute("maxOccurs") == null)
                        {
                            // Get list of custom class
                            main.Properties.Add(new PropertySchema()
                            {
                                Type = PropertyType.Custom,
                                Name = element.Attribute("name").Value,
                                CustomClass = GetChildClassSchema(element)
                            });
                        }
                        else
                        {
                            main.Properties.Add(new PropertySchema()
                            {
                                Type = PropertyType.List,
                                ListType = PropertyType.Custom,
                                XmlType = PropertyXmlType.Element,
                                Name = element.Attribute("name").Value,
                                CustomClass = GetChildClassSchema(element)
                            });
                        }
                    }
                }
            }

            return main;
        }

        #endregion
    }
}
